﻿using System;
using System.IO;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using ThreadState = System.Threading.ThreadState;

namespace ExcelRefresh.Excel
{
    public class ExcelRefreshEngine : IDisposable
    {
#if DEBUG
        private static readonly TimeSpan ThreadSleepDuration = new TimeSpan(0, 0, 1);
#else
        private static readonly TimeSpan ThreadSleepDuration = new TimeSpan(0, 0, 30);
#endif
        private bool _disposing;

        private readonly TimeSpan _refreshTimeout;
        private readonly Thread[] _threads;
        private readonly List<FileToRefresh> _inProgress;
        private readonly IProducerConsumerCollection<FileToRefresh> _files;
        private readonly IProducerConsumerCollection<FileToRefresh> _results;

        public ExcelRefreshEngine(int numberOfThreads, TimeSpan refreshTimeout, IProducerConsumerCollection<FileToRefresh> files)
        {
            _files = files;
            _refreshTimeout = refreshTimeout;
            _inProgress = new List<FileToRefresh>();
            _results = new ConcurrentQueue<FileToRefresh>();

            _threads = Enumerable
                .Range(0, numberOfThreads)
                .Select(i => new Thread(QueueProcessorLoop) { IsBackground = false })
                .ToArray();

            Array.ForEach(_threads, t => t.Start());
        }

        public void Dispose()
        {
            if (_disposing) return;
            _disposing = true;

            foreach (var thread in _threads.Where(thread => thread.IsAlive))
                thread.Abort();

            foreach (var thread in _threads.Where(thread => thread.IsAlive))
                thread.Join();
        }

        private void QueueProcessorLoop()
        {
            while (true)
            {
                FileToRefresh file;
                
                if (_files.TryTake(out file))
                    ProcessFile(file);
                else
                    Thread.Sleep(ThreadSleepDuration);
             
                if (_disposing)
                    return;
            }
        }

        private void ProcessFile(FileToRefresh file)
        {
            lock (_inProgress)
                _inProgress.Add(file);

            var workerThread = new Thread(fi => ProcessFileDoWork((FileToRefresh)file));
            try
            {
                workerThread.TrySetApartmentState(ApartmentState.STA);
                workerThread.Start(file);
                
                //workerThread.Join();        // ignoring the refreesh timeout for now!
                //workerThread.Join(_refreshTimeout);
                var timeout = DateTime.Now.Add(_refreshTimeout);
                while (workerThread.IsAlive && DateTime.Now < timeout)
                    Thread.Sleep(3000);
            }
            finally
            {
                if (workerThread.IsAlive)
                {
                    workerThread.Abort();
                    workerThread.Join();
                }
            }
        }

        /// <summary>
        /// Runs a thread and processes an Excel file.
        /// </summary>
        /// <param name="fileInfo"></param>
        private void ProcessFileDoWork(FileToRefresh file)
        {
            try
            {
                file.StartTime = DateTime.Now;
                ExcelHelpers.RefreshAllWorkbookData(file);
            }
            catch (FileInUseException ex)
            {
                file.ExceptionThrown = ex;
                //_files.TryAdd(fileInfo.FilePath);
            }
            catch (ThreadAbortException ex)
            {
                file.ExceptionThrown = ex;
                throw;
            }
            catch (Exception ex)
            {
                file.ExceptionThrown = ex;
            }
            finally
            {
                file.EndTime = DateTime.Now;

                lock (_inProgress)
                    _inProgress.Remove(file);

                _results.TryAdd(file);
            }
        }
    }
}